#include "lua_function.h"

#include <stdlib.h>
#include <string.h>

#include <event.h>

#include <assert.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <tchdb.h>

#include "log.h"

#include "limit_resources_manager.h"

#define LUA_CALL(L, IN, OUT) lua_call(L, IN, OUT)

static struct event_base * main_base = 0;
//extern lua_State * main_L;
static resid_t bev_manager_id = INVALIDATE_ID;

static struct ResourcesOperation conn_opt;

struct network {
	resid_t id;
	uint32_t len;
	struct bufferevent * bev;
	lua_State * L;
};

static void * cb_bev_alloc(void * ctx)
{
	return malloc(sizeof(struct network));
}

static int cb_bev_init(resid_t id, void * res, void * init_data, void * ctx)
{
	struct network * conn = (struct network*)res;
	conn->id = id;
	conn->len = 0;
	conn->L = 0;
	conn->bev = bufferevent_socket_new(main_base,
			-1, BEV_OPT_CLOSE_ON_FREE);
	return 0;
}

static void cb_bev_destory(resid_t id, void * res, void * ctx)
{
	struct network * conn = (struct network*)res;
	conn->id = INVALIDATE_ID;
	conn->len = 0;
	conn->L = 0;
	if (conn->bev) {
		bufferevent_free(conn->bev);
	}
	conn->bev = 0;
}

static void cb_bev_free(void * res, void * ctx)
{
	free(res);
}

struct network * alloc_network()
{
	if (bev_manager_id == INVALIDATE_ID) {
		LRMStartup(10);
		conn_opt.alloc = cb_bev_alloc;
		conn_opt.free = cb_bev_free;
		conn_opt.init = cb_bev_init;
		conn_opt.destory = cb_bev_destory;
		bev_manager_id = LRMCreate(1024, &conn_opt, 0);
		if (bev_manager_id == INVALIDATE_ID) {
			return 0;
		}
	}

	resid_t conn_id = LRMAllocResources(bev_manager_id, 0);
	struct network * conn = (struct network*)LRMGetResources(bev_manager_id, conn_id);
	return conn;
}

void free_network(struct network * conn)
{
	LRMReleaseResources(bev_manager_id, conn->id);
}

static int network_close(lua_State * L)
{
	if(!lua_isnumber(L, -1)) {
		return 0;
	}

	int id = lua_tointeger(L, -1);
	struct network * conn = (struct network*)LRMGetResources(bev_manager_id, id);
	if (conn == 0) {
		return 0;
	}

	free_network(conn);
	lua_pushboolean(L, 0);
	return 1;
}

static int network_write(lua_State * L)
{
	if(!lua_isnumber(L, -2)) {
		return 0;
	}

	if(!lua_isstring(L, -1)) {
		return 0;
	}

	int id = lua_tointeger(L, -2);
	struct network * conn = (struct network*)LRMGetResources(bev_manager_id, id);
	if (conn == 0) {
		return 0;
	}

	struct bufferevent * bev = conn->bev;
	if (bev == 0) {
		return 0;
	}

	size_t len = 0;
	const char * ptr = lua_tolstring(L, -1, &len);
	bufferevent_write(bev, ptr, len);
	lua_pushinteger(L, len);
	return 1;
}


static int read_one_message(struct network * conn, char * buf, int buf_len)
{
	struct evbuffer * buffer = bufferevent_get_input(conn->bev);
	int fd = bufferevent_getfd(conn->bev);
	if (conn->len == 0) {
		size_t recv_len = evbuffer_get_length(buffer);
		if (recv_len < sizeof(uint32_t)) { return 0; }
		bufferevent_read(conn->bev, &(conn->len), sizeof(uint32_t));
	}

	if (conn->len > (uint32_t)buf_len) {
		WRITE_ERROR_LOG("[%d] message is too long %u", fd, conn->len);
		return -1;
	}

	size_t recv_len = evbuffer_get_length(buffer);
	if (recv_len  < conn->len - sizeof(uint32_t)) {
		return 0;
	}

	memcpy(buf, &(conn->len),  sizeof(uint32_t));
	bufferevent_read(conn->bev, buf + sizeof(uint32_t), conn->len - sizeof(uint32_t));
	int ret = conn->len;
	conn->len = 0;
	return ret;
}

static void on_read(struct bufferevent * bev, void *ctx)
{
	struct network * conn = (struct network*)ctx;
	assert(bev == conn->bev);
	while(1) {
		char data[4096] = {0};
		int package_len = read_one_message(conn, data, sizeof(data));
		if (package_len == -1) {
			WRITE_ERROR_LOG("package error!!!\n");
			lua_getglobal(conn->L, "on_error");
			if (!lua_isfunction(conn->L, -1)) {
				lua_pop(conn->L, 1);
			} else {
				lua_pushinteger(conn->L, conn->id);
				LUA_CALL(conn->L, 1, 0);
			}
			free_network(conn);
			return;
		}

		if (package_len == 0) {
			break;
		}

		lua_getglobal(conn->L, "process_message");
		if (!lua_isfunction(conn->L, -1)) {
			WRITE_DEBUG_LOG("process_message is not a function on script");	
			lua_pop(conn->L, 1);
		} else {
			lua_pushinteger(conn->L, conn->id);
			lua_pushlstring(conn->L, data, package_len);
			LUA_CALL(conn->L, 2, 0);
		}
	}
}

/*
static void on_write(struct bufferevent *bev, void *ctx)
{
	struct network * info = (struct network*)ctx;
	if (max_loop > 0 && info->send_count>= max_loop) {
		bufferevent_disable(bev, EV_WRITE);
		return;
	}
	send_message(bev, info);
}
*/

static void on_event(struct bufferevent *bev, short what, void *ctx)
{
	struct network * conn = (struct network*)ctx;
	//struct event_base * base = bufferevent_get_base(bev);
	//int fd = bufferevent_getfd(bev);
	if (what&BEV_EVENT_ERROR) {
		//printf("[%d] error 0x%x\n", fd, what);
		//event_base_loopbreak(base);
		lua_getglobal(conn->L, "on_error");
		if (!lua_isfunction(conn->L, -1)) {
			lua_pop(conn->L, 1);
		} else {
			lua_pushinteger(conn->L, conn->id);
			LUA_CALL(conn->L, 1, 0);
		}
		free_network(conn);
		return;
	}

	if (what&BEV_EVENT_CONNECTED) {
		//printf("[%d] connected\n", fd);
		//send_message(bev, info);
		lua_getglobal(conn->L, "on_connected");
		if (!lua_isfunction(conn->L, -1)) {
			lua_pop(conn->L, 1);
		} else {
			lua_pushinteger(conn->L, conn->id);
			LUA_CALL(conn->L, 1, 0);
		}
	}

	if (what & BEV_EVENT_EOF) {
		lua_getglobal(conn->L, "on_closed");
		if (!lua_isfunction(conn->L, -1)) {
			lua_pop(conn->L, 1);
		} else {
			lua_pushinteger(conn->L, conn->id);
			LUA_CALL(conn->L, 1, 0);
		}
		free_network(conn);
		return;
	}
}

static int network_connect(lua_State * L)
{
	if(!lua_isstring(L, -2)) {
		return 0;
	}

	if(!lua_isnumber(L, -1)) {
		return 0;
	}

	const char * host = lua_tostring(L, -2);
	int port = lua_tointeger(L, -1);

	struct network * conn = alloc_network();
	struct bufferevent * bev = conn->bev;


	if (bufferevent_socket_connect_hostname(bev, NULL, AF_INET,
			host, port) == -1) {
		WRITE_ERROR_LOG("connect to %s:%d failed\n", host, port);
		free_network(conn);
		return 0;
	}
	WRITE_ERROR_LOG("connect to %s:%d success", host, port);
	conn->L = L;
	bufferevent_setcb(bev, on_read, NULL, on_event, conn);
	bufferevent_enable(bev, EV_READ);
	lua_pushinteger(L, conn->id);
	return 1;
}

static int socktoaddr(const char* host, unsigned short port, 
		struct sockaddr_in *addr)
{
	struct hostent *host_ent;
	memset(addr, 0, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_port   = htons(port);
	//获得主机信息入口
	if ((host_ent=gethostbyname(host))!=NULL) {
		memcpy(&addr->sin_addr, host_ent->h_addr, host_ent->h_length);
	} else {
		//xxx.xxx.xxx.xxx
		if ((addr->sin_addr.s_addr = inet_addr(host)) == INADDR_NONE) {
			return -1;
		}
	}
	return 0;
}


static int setnblock(int iSock)
{
	int iFlags;
	iFlags = fcntl(iSock, F_GETFL, 0);
	iFlags |= O_NONBLOCK;
	iFlags |= O_NDELAY;
	fcntl(iSock, F_SETFL, iFlags);
	return 0;
}

static int setreuse(int socket)
{
	/*将socket设置为重用模式*/
	int flag = 1;
	if (setsockopt(socket, SOL_SOCKET, SO_REUSEADDR,
				(char *)&flag, sizeof(flag)) != 0) {
		return -1;
	}
	return 0;
}

static int listen_on(const char * ip, unsigned short port, int backlog)
{
	int fd;
	struct sockaddr_in addr;
	if (socktoaddr(ip, port, &addr) != 0) {
		return -1;
	}

	fd = socket(PF_INET, SOCK_STREAM, 0);
	if (fd == -1) {
		return -1;
	}

	if (setreuse(fd) != 0 || 
			setnblock(fd) != 0 ||
			bind(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0 ||
			listen(fd, backlog) != 0) {
		close(fd);
		return -1;
	}
	return fd;  
}

struct accept_info {
	struct event * ev;
	lua_State * L;
};

static void on_accept(evutil_socket_t fd, short event, void * p)
{
	assert(event == EV_READ);
	struct accept_info * info = (struct accept_info*)p;

	struct event * ev = info->ev;
	lua_State * L = info->L;
	struct event_base * base = event_get_base(ev);

	int max_recv_count = 0xfffffff;
	do {
		struct sockaddr_in addr;
		socklen_t addr_len = sizeof(addr);
		int client_fd = accept(fd, (struct sockaddr*)&addr, &addr_len);
		if (client_fd == -1) {
			if (errno == EINTR) continue;
			if (errno == EWOULDBLOCK || errno == EINPROGRESS) {
				return;
			}
			WRITE_INFO_LOG("socket %d accept error: %d-%s",
					fd, errno, strerror(errno));
			event_del(ev);
			free(ev);
			close(fd);
			event_base_loopbreak(base);
			return;
		}

		struct network * conn = alloc_network();
		if (conn == 0) {
			WRITE_WARNING_LOG("to many network exist, close %d\n", client_fd);
			close(client_fd);
			continue;
		}

		conn->L = L;

		WRITE_DEBUG_LOG("socket(id:%d,fd:%d) connected",
				conn->id, client_fd);
		struct bufferevent * bev = conn->bev;

		bufferevent_setfd(bev, client_fd);

		bufferevent_setcb(bev, on_read, NULL, on_event, conn);
		bufferevent_enable(bev, EV_READ);

		lua_getglobal(conn->L, "on_accept");
		if (!lua_isfunction(conn->L, -1)) {
			printf("on_accept is not a function on script");	
			lua_pop(conn->L, 1);
		} else {
			lua_pushinteger(conn->L, conn->id);
			LUA_CALL(conn->L, 1, 0);
		}
	} while(--max_recv_count);
}

static int ev_listen_on(struct event_base * base,
		const char * host, int port, int backlog,
		lua_State * L)
{
	int fd = listen_on(host, port, backlog);
	if (fd == -1) {
		WRITE_ERROR_LOG("listen on %s:%d failed: %s",
				host, port, strerror(errno));
		return -1;
	}

	struct event * ev = (struct event*)malloc(sizeof(struct event));
	struct accept_info * info = (struct accept_info*)malloc(sizeof(struct accept_info));
	info->L = L;
	info->ev = ev;
	if (event_assign(ev, base, fd, EV_READ|EV_PERSIST,
				on_accept, info) != 0) {
		WRITE_DEBUG_LOG("event_assign failed!!!");
		free(ev);
		close(fd);
		return -1;
	}

	if (event_add(ev, NULL) != 0) {
		WRITE_DEBUG_LOG("event_add failed!!!");
		free(ev);
		close(fd);
		return -1;
	}
	return fd;
}

static int network_listen(lua_State * L)
{
	if(!lua_isstring(L, -2)) {
		return 0;
	}

	if(!lua_isnumber(L, -1)) {
		return 0;
	}

	const char * host = lua_tostring(L, -2);
	int port = lua_tointeger(L, -1);
	int backlog = 10;

	if (ev_listen_on(main_base,host, port, backlog, L) < 0) {
		return 0;
	}

	WRITE_INFO_LOG("listen on %s:%d", host, port);

	struct network * conn = alloc_network();
	conn->L = L;
	lua_pushinteger(L, conn->id);
	return 1;
}

luaL_Reg network_functions[] = {
	{"close", network_close},
	{"write", network_write},
	{"connect", network_connect},
	{"listen", network_listen},
	{0,0},
};


#ifdef __cplusplus
extern "C" 
#endif 
int registe_network_function(lua_State * L, struct event_base * base)
{
	main_base = base;
	luaL_register(L, "network", network_functions);
	return 0;
}
